﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.AddIn.Hosting;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Transactions;
using BlueCurve.AddinHostView;
using BlueCurve.AddInsLoad;
using BlueCurve.EnumAction;
using BlueCurve.IndexationContract;
using BlueCurve.SearchContract;
using BlueCurve.WebCrawlerRequest;
using Indexer.Remoting;
using Microsoft.Win32.SafeHandles;

namespace Indexer
{
    /// <summary>
    /// Class contenant les méthodes de lancement des indexeurs
    /// </summary>
    public class Indexation : BlueCurve.Common.Transaction.FileGenerationService.FileGeneration
    {
        #region 'Configuration'

        /// <summary>
        /// Permet de laisser tourner le thread courant
        /// </summary>
        /// <value>true</value>
        private bool m_IsRun = true;
        /// <summary>
        /// Collection contenant les chemins de fichiers des documents à indexer
        /// </summary>
        private Queue m_Queue = new Queue();
        /// <summary>
        /// Objet pour la synchronisation de la this.m_Queue
        /// </summary>
        private readonly object m_ThreadsLock = new object();
        /// <summary>
        /// Nombre de threads d'indexation actuellement en fonction
        /// </summary>
        private long m_AliveThreads = 1;
        /// <summary>
        /// Objet pour le chargement des Addins
        /// </summary>
        private PluginsLoader<AddinFileViewHostSide> m_Pluginsloader = new PluginsLoader<AddinFileViewHostSide>(@"AddIns");
        /// <summary>
        /// Déclenche la demande de nouvelles données à indexer auprès du scheduler
        /// </summary>
        public event EventHandler GetFilesToIndex;
        
        /// <summary>
        /// Arrete le pool de threads
        /// </summary>
        public bool IsStop
        {
            set { this.m_IsRun = false; }
        }

        /// <summary>
        /// Retourne le nombre de thread d'indexation en cours
        /// </summary>
        public long AliveThreads
        {
            get { return Interlocked.Read(ref this.m_AliveThreads);}
        }

        /// <summary>
        /// Constructeur
        /// </summary>
        public Indexation()
        {
           this.m_Pluginsloader.UpdateAddins();
        }

        #endregion



        #region 'Functions'

        /// <summary>
        /// Fonction principale de la classe
        /// </summary>
        public void RunIndexation()
        {
            this.Reload(); // Rechargement de la pile d'indexation
            while(this.m_IsRun)
            {
                if (this.m_Queue.Count == 0)
                {
                    using (IndexerdbDataContext db = new IndexerdbDataContext())
                    {
                        Nullable<int> count = db.CountItemsInTheCache();

                        if (count.HasValue && count != null)
                        {
                            if (count.Value == 0)
                                GetFilesToIndex("", EventArgs.Empty);
                            else
                               this.m_Queue = new Queue(db.GetPath().ToList());
                        }
                    }
                }

                while (this.m_Queue.Count > 0)
                {
                    ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), this.m_Queue.Dequeue());
                }

                if (!this.m_IsRun)
                    break;
                
                Thread.Sleep(25 * 1000); 
            }
            while (this.AliveThreads > 1) { Thread.Sleep(150); }
            if(this.m_Queue.Count > 0)
                this.Save();

            Interlocked.Decrement(ref this.m_AliveThreads);
        }



        /// <summary>
        /// Fonction attachée au pool de threads
        /// </summary>
        /// <param name="data">Données de travail du thread</param>
        private void DoWork(object data)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            string path = data as string;
            try
            {
                if (string.IsNullOrEmpty(path))
                    throw new Exception("Le chemin de fichier est vide");

                RequestState rs = this.ReadFile(string.Concat(path, @"\requeststate.bin"));

                if (rs == null)
                    throw new Exception("L'objet RequestState est null");
         
                // Permissions d'accès
                PermissionSet perms = new PermissionSet(PermissionState.None);
                // Token de l'addin
                AddInToken addinToken = null;
                // Méta données pour la mise à jour
                UpdateMetaData update = null;

                switch (rs.Action)
                {
                    case IndexAction.DELETE:
                    case IndexAction.INDEX:
                        break;
                    case IndexAction.UPDATE:
                        // Demande des informations de mise à jour
                        Tcpclient client = new Tcpclient();
                        update = client.GetMetaDataForUpdate(rs.Uri.ToString());                 
                        break;
                    default: throw new Exception("Le type d'action à effectuer sur le document n'est pas valide");
                }

                switch (rs.TypeOffRequest)
                {
                    case RequestType.http:
                        addinToken = this.m_Pluginsloader.GetAddInToken(rs.Headers[HttpResponseHeader.ContentType] ?? "");
                        if (rs.Action == IndexAction.UPDATE)
                        {
                            if (update == null)
                                throw new Exception("Les méta données pour la mise à jour étaient vide");
                            else
                            {
                                DateTime _cLastModified = DateTime.Parse(rs.Headers[HttpResponseHeader.LastModified]);
                                if (update.LastModified.Equals(_cLastModified))
                                    throw new Exception(string.Format("Une mise à jour du document {0} n'est pas nécessaire", rs.Uri.ToString()));
                            }
                        }
                        break;
                    case RequestType.ftp:
                    case RequestType.share:
                        if (rs.TypeOffRequest == RequestType.ftp)
                            throw new NotImplementedException("Le support du protocol ftp n'a pas encore été implémenté");

                        addinToken = this.m_Pluginsloader.GetAddInToken(Path.GetExtension(rs.Uri.ToString()) ?? "");

                        if (rs.Action == IndexAction.UPDATE)
                        {
                            if (update == null)
                                throw new Exception("Les méta données pour la mise à jour étaient vide");
                            else
                            {
                                DateTime _cLastModified;
                                if (rs.TypeOffRequest == RequestType.ftp)
                                    _cLastModified = rs.LastModified;
                                else
                                    _cLastModified = rs.CreationDate;

                                if (update.LastModified.Equals(_cLastModified))
                                    throw new Exception(string.Format("Une mise à jour du document \"{0}\" n'est pas nécessaire", rs.Uri.ToString()));
                            }
                        }
                        perms.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, rs.Uri.ToString()));
                        break;
                    case RequestType.gopher:
                        throw new NotImplementedException("Le support du protocol gopher n'a pas encore été implémenté");
                       // break;
                    default: throw new Exception(string.Format("Le type de requete \"{0}\" est inconnu ou non implémenté", rs.TypeOffRequest));
                }



                if (addinToken != null)
                {
                    // Encore qq permissions
                    perms.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
                    perms.AddPermission(new EnvironmentPermission(PermissionState.Unrestricted));
                    perms.AddPermission(new IsolatedStorageFilePermission(PermissionState.Unrestricted));
                    perms.AddPermission(new StorePermission(PermissionState.Unrestricted));
                    perms.AddPermission(new WebPermission(PermissionState.Unrestricted));
                    perms.AddPermission(new DnsPermission(PermissionState.Unrestricted));

                    // Activation du plugin
                    AddinFileViewHostSide addinInstance = addinToken.Activate<AddinFileViewHostSide>(perms);

                    // Appel du plugin
                    IndexationData Data = addinInstance.IndexerLoader(rs, path);

                    //Fermeture du canal avec le plugin
                    AddInController.GetAddInController(addinInstance).Shutdown();

                    // Ajout de données pour l'envoie vers le Queryserver
                    DataTreatment.GetInstance.Add(Data);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                using (IndexerdbDataContext db = new IndexerdbDataContext())
                {
                    db.DeletePath(path);
                }
            }
            Interlocked.Decrement(ref this.m_AliveThreads);
        }
        


        /// <summary>
        /// Désérialise l'objet de la requete
        /// </summary>
        /// <param name="path">Chemin d'accès du fichier</param>
        /// <returns>RequestState</returns>
        new public RequestState ReadFile(string path)
        {
            RequestState rs = null;
            SafeTransactionHandle txHandle = null;
            SafeFileHandle fileHandle = null;

            try
            {
                IKernelTransaction kernelTx = (IKernelTransaction)TransactionInterop.GetDtcTransaction(System.Transactions.Transaction.Current);
                kernelTx.GetHandle(out txHandle);

                fileHandle
                    = CreateFileTransacted(
                        path
                        , SafeTransactionHandle.FileAccess.GENERIC_READ
                        , SafeTransactionHandle.FileShare.FILE_SHARE_READ
                        , IntPtr.Zero
                        , SafeTransactionHandle.FileMode.OPEN_ALWAYS
                        , 0
                        , IntPtr.Zero
                        , txHandle
                        , IntPtr.Zero
                        , IntPtr.Zero);

                if (fileHandle.IsInvalid)
                    throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());

                using (FileStream stream = new FileStream(fileHandle, FileAccess.Read, 1024, false))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    rs = (RequestState)formatter.Deserialize(stream);
                    stream.Close();
                }

            } catch {
            }
            finally
            {
                if (fileHandle != null && !fileHandle.IsInvalid)
                {
                    fileHandle.Close();
                    fileHandle.Dispose();
                }

                if (txHandle != null && !txHandle.IsInvalid)
                {
                    txHandle.Close();
                    txHandle.Dispose();
                }
            }
            return rs;
        }
        


        /// <summary>
        /// Sauvegarde la pile d'indexation
        /// </summary>
        private void Save()
        {
            using (FileStream stream = File.Open("Indexationthis.m_Queue.bin", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this.m_Queue);
                stream.Close();
            }
        }
        


        /// <summary>
        /// Recharge la pile d'indexation
        /// </summary>
        private void Reload()
        {
            if (File.Exists("Indexationthis.m_Queue.bin"))
            {
                System.Collections.Queue queue;
                using (FileStream stream = File.Open("Indexationthis.m_Queue.bin", FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    queue = (System.Collections.Queue)formatter.Deserialize(stream);
                    stream.Close();
                }

                while (queue.Count > 0)
                {
                   this.m_Queue.Enqueue((string)queue.Dequeue());
                }

                File.Delete("Indexationthis.m_Queue.bin");
            }
        }

        #endregion
    }
}
